package com.dream.libxrec.player.audio;

import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Build;
import android.util.Log;

import java.io.IOException;
import java.nio.ByteBuffer;

public class AudioDecoder {

    private final String TAG = "AudioDecoder";
    private volatile boolean mStop = true;
    private volatile boolean isPause = false;
    private volatile boolean isSeeking = false;
    private volatile long mSeekPts = -1L;
    private final Object mPauseLock = new Object();
    private final Object mSeekLock = new Object();
    private final Object mEndLock = new Object();
    //读取到文件结尾等待进一步操作（退出或seek等）的锁，避免直接释放了资源

    private MediaExtractor mMediaExtractor = null;
    private MediaCodec mMediaCodec = null;
    private IAudioDecodeCallback mCB = null;
    private MediaFormat mMediaFormat = null;
    private long mDurationUs = 0;//音频轨总时长，单位为微秒
    private boolean isEndRelease = false;//读到文件结尾时自动释放资源

    /**
     *
     * @param videoFilePath 音视频文件的地址
     * @param cb 回调
     * @return 如果不存在音频轨道，则返回false，存在且支持该音频类型解码，则返回true
     * @throws Exception
     */
    public boolean init(String videoFilePath,
                     IAudioDecodeCallback cb) throws Exception {
        
        this.mCB = cb;
        
        mMediaExtractor = new MediaExtractor();
        mMediaExtractor.setDataSource(videoFilePath);
        int audioTrackIndex = selectAudioTrack(mMediaExtractor);
        if (audioTrackIndex < 0) {
            Log.e(TAG, "No audio track found in " + videoFilePath);
            return false;
        }
        MediaFormat mediaFormat =  mMediaExtractor.getTrackFormat(audioTrackIndex);

        String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
        int sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
        int channelCount = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
        mDurationUs = mediaFormat.getLong(MediaFormat.KEY_DURATION);

        Log.i(TAG, "decodeAudio: " +
                "sample: " + sampleRate+";"+
                "channelCount:"+channelCount+";"+
                "mimeType: " + mimeType+";"+
                "durationUs: "+mDurationUs
                );

        this.mMediaFormat = mediaFormat;
        if (mCB != null){
            mCB.init(mediaFormat);
        }

        mMediaCodec = MediaCodec.createDecoderByType(mimeType);
        mMediaCodec.configure(mediaFormat, null, null, 0);

        return true;
    }
    
    /**
     * AAC 格式解码成 PCM 数据
     *
     * @throws IOException
     */
    public void start() throws IOException {
        mStop = false;
        mMediaCodec.start();

        final long kTimeOutUs = 10_000;
        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        boolean sawInputEOS = false;
        boolean sawOutputEOS = false;

        try {
            while (!mStop&&!sawOutputEOS) {
                if (!sawInputEOS) {
                    int inputBufIndex = mMediaCodec.dequeueInputBuffer(kTimeOutUs);
                    if (inputBufIndex >= 0) {
                        ByteBuffer dstBuf =
                                getMediaCodecInputBuffer(mMediaCodec,inputBufIndex);
                        int sampleSize = mMediaExtractor.readSampleData(dstBuf, 0);

                        if (sampleSize < 0) {
                            if (isEndRelease){
                                // 读取到文件末尾,通知解码器结束
                                Log.i(TAG, "saw input EOS.");
                                sawInputEOS = true;
                                mMediaCodec.queueInputBuffer(inputBufIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            }else{
                                synchronized (mEndLock){
                                    try {
                                        mEndLock.wait();
                                    } catch (InterruptedException e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                        } else {
                            mMediaCodec.queueInputBuffer(inputBufIndex, 0, sampleSize, mMediaExtractor.getSampleTime(), 0);
                            mMediaExtractor.advance();
                        }
                    }
                }

                int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(info, kTimeOutUs);
                if (outputBufferIndex >= 0) {

                    if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                        Log.i(TAG, "audio encoder: mMediaCodec config buffer");
                        mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                        continue;
                    }

                    if (info.size != 0) {
                        ByteBuffer outBuf =
                                getMediaCodecOutputBuffer(mMediaCodec,outputBufferIndex);;
                        outBuf.position(info.offset);
                        outBuf.limit(info.offset + info.size);
                        byte[] data = new byte[info.size];
                        outBuf.get(data);

                        if (mCB!= null&&!isPause&&!isSeeking){
                            mCB.onDecode(data,info.size,info.presentationTimeUs);
                        }
                    }

                    mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                        Log.i(TAG, "saw output EOS.");
                        sawOutputEOS = true;
                    }
                } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                    Log.i(TAG, "output buffers have changed.");
                } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    MediaFormat format = mMediaCodec.getOutputFormat();
                    Log.i(TAG, "output format has changed to " + format);
                }

                if (isPause&&!mStop){
                    synchronized (mPauseLock){
                        try {
                            mPauseLock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }

                if (isSeeking && mSeekPts >= 0){
                    mMediaExtractor.seekTo(mSeekPts,
                            MediaExtractor.SEEK_TO_CLOSEST_SYNC);
                    mMediaCodec.flush();// 清空MediaCodec缓冲数据

                    Log.i(TAG, "seekTo: " + mSeekPts);
                    isSeeking = false;
                    mSeekPts = -1;
                }
            }
        } finally {
            release();
        }
    }

    private ByteBuffer getMediaCodecInputBuffer(MediaCodec codec,
                                                int index){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
            return codec.getInputBuffer(index);
        }else{
            ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
            return codecInputBuffers[index];
        }
    }

    private ByteBuffer getMediaCodecOutputBuffer(MediaCodec codec,
                                                int index){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
            return codec.getOutputBuffer(index);
        }else{
            ByteBuffer[] buffers = codec.getOutputBuffers();
            return buffers[index];
        }
    }

    public boolean isDecoding(){
        return!mStop;
    }

    public boolean isPause(){
        return isPause;
    }

    public void onPause(boolean isPause){
        this.isPause = isPause;
        if (!isPause){
            synchronized (mPauseLock){
                mPauseLock.notifyAll();
            }
        }
    }

    /**
     * 是否在文件结尾时自动释放资源（如果为true，播放结束后会自动释放，但无法再进行seek），默认为false
     * @param isEndRelease
     */
    public void isEndRelease(boolean isEndRelease){
        this.isEndRelease = isEndRelease;
    }

    public void close(){
        if (!mStop){
            mStop = true;
            if (isPause){
                isPause = false;
                synchronized (mPauseLock){
                    mPauseLock.notifyAll();
                }
            }

            if (isSeeking){
                isSeeking = false;
                synchronized (mSeekLock){
                    mSeekLock.notify();
                }
            }

            synchronized (mEndLock){
                mEndLock.notify();
            }
        }else{
            release();
        }
    }

    private void release(){
        mStop = true;
        isPause = false;

        if (mMediaCodec != null){
            try {
                mMediaCodec.release();
            } catch (Exception ignored) {}

            try {
                mMediaCodec.stop();
            } catch (Exception ignored) {}

            mMediaCodec = null;
        }

        mMediaFormat = null;

        if (mMediaExtractor != null){
            try {
                mMediaExtractor.release();
            }catch (Exception ignored){}

            mMediaExtractor = null;
        }

        Log.i(TAG,"AudioDecoder release finish");

        if (mCB != null){
            mCB.onFinish();
        }
    }

    public MediaFormat getFormat(){
        return mMediaFormat;
    }

    private int selectAudioTrack(MediaExtractor extractor) {
        int numTracks = extractor.getTrackCount();
        for (int i = 0; i < numTracks; i++) {
            MediaFormat format = extractor.getTrackFormat(i);
            String mime = format.getString(MediaFormat.KEY_MIME);
            if (mime!=null&&mime.startsWith("audio/")) {
                extractor.selectTrack(i);
                return i;
            }
        }
        return -1;
    }

    public long getDurationUs(){
        return mDurationUs;
    }

    public synchronized void seekTo(long pts) {
        if (mMediaExtractor== null){
            Log.i(TAG,"audio media extractor is release");
            return;
        }

        this.isSeeking = true;
        this.mSeekPts = pts;

        synchronized (mEndLock){
            mEndLock.notifyAll();
        }
    }

    public boolean isSeeking(){
        return isSeeking;
    }
}
